import { Container, InProperties, BaseOutProperties, RenderContext, abortableEffect } from '@pmndrs/uikit'
import { signal, computed } from '@preact/signals-core'
import { colors, componentDefaults } from '../theme.js'
import { Object3DEventMap, Vector3 } from 'three'

const vectorHelper = new Vector3()

export type SliderOutProperties = {
  disabled?: boolean
  value?: number | string
  min?: number | string
  max?: number | string
  step?: number | string
  defaultValue?: number | string
  onValueChange?: (value: number) => void
} & BaseOutProperties

export type SliderProperties = InProperties<SliderOutProperties>

export class Slider extends Container<SliderOutProperties> {
  private downPointerId?: number

  public readonly uncontrolledSignal = signal<number | undefined>(undefined)
  public readonly currentSignal = computed(() =>
    Number(this.properties.value.value ?? this.uncontrolledSignal.value ?? this.properties.value.defaultValue ?? 0),
  )

  public readonly track: Container
  public readonly fill: Container
  public readonly thumb: Container

  constructor(
    inputProperties?: InProperties<SliderOutProperties>,
    initialClasses?: Array<InProperties<BaseOutProperties> | string>,
    config?: {
      renderContext?: RenderContext
      defaultOverrides?: InProperties<SliderOutProperties>
    },
  ) {
    super(inputProperties, initialClasses, {
      defaults: componentDefaults,
      ...config,
      defaultOverrides: {
        '*': {
          borderColor: colors.border,
        },
        positionType: 'relative',
        flexDirection: 'column',
        height: 8,
        width: '100%',
        alignItems: 'center',
        ...config?.defaultOverrides,
      },
    })

    abortableEffect(() => {
      if (this.properties.value.disabled ?? false) {
        return
      }
      const pointerUpListener = (e: Object3DEventMap['pointerup']) => {
        if (this.downPointerId == null) {
          return
        }
        this.downPointerId = undefined
        e.stopPropagation?.()
      }
      const pointerDownListener = (e: Object3DEventMap['pointerdown']) => {
        if (this.downPointerId != null) {
          return
        }
        this.downPointerId = e.pointerId
        this.handleSetValue(e)
        if (
          'target' in e &&
          e.target != null &&
          typeof e.target === 'object' &&
          'setPointerCapture' in e.target &&
          typeof e.target.setPointerCapture === 'function'
        ) {
          e.target.setPointerCapture(e.pointerId)
        }
      }
      const pointerMoveListener = (e: Object3DEventMap['pointermove']) => {
        if (this.downPointerId != e.pointerId) {
          return
        }
        this.handleSetValue(e)
      }
      this.addEventListener('pointermove', pointerMoveListener)
      this.addEventListener('pointerup', pointerUpListener)
      this.addEventListener('pointerdown', pointerDownListener)
      return () => {
        this.removeEventListener('pointermove', pointerMoveListener)
        this.removeEventListener('pointerup', pointerUpListener)
        this.removeEventListener('pointerdown', pointerDownListener)
      }
    }, this.abortSignal)

    const percentage = computed(() => {
      const min = Number(this.properties.value.min ?? 0)
      const max = Number(this.properties.value.max ?? 100)
      const range = max - min
      return `${(100 * (this.currentSignal.value - min)) / range}%` as const
    })

    // Create track
    this.track = new Container(undefined, undefined, {
      defaults: componentDefaults,
      defaultOverrides: {
        '*': {
          borderColor: colors.border,
        },
        height: 8,
        positionType: 'absolute',
        positionLeft: 0,
        positionRight: 0,
        flexGrow: 1,
        borderRadius: 1000,
        backgroundColor: colors.secondary,
      },
    })

    // Create fill
    this.fill = new Container(undefined, undefined, {
      defaults: componentDefaults,
      defaultOverrides: {
        '*': {
          borderColor: colors.border,
        },
        height: '100%',
        width: percentage,
        borderRadius: 1000,
        backgroundColor: colors.primary,
      },
    })

    this.track.add(this.fill)
    super.add(this.track)

    // Create thumb
    this.thumb = new Container(undefined, undefined, {
      defaults: componentDefaults,
      defaultOverrides: {
        '*': {
          borderColor: colors.border,
        },
        zIndexOffset: 100,
        positionType: 'absolute',
        positionLeft: percentage,
        transformTranslateX: -10,
        transformTranslateY: -6,
        cursor: 'pointer',
        opacity: computed(() => ((this.properties.value.disabled ?? false) ? 0.5 : undefined)),
        height: 20,
        width: 20,
        borderWidth: 2,
        borderRadius: 1000,
        borderColor: colors.primary,
        backgroundColor: colors.background,
      },
    })

    super.add(this.thumb)
  }

  private handleSetValue(e: { stopPropagation?: () => void; point: Vector3 }) {
    vectorHelper.copy(e.point)
    this.worldToLocal(vectorHelper)
    const minValue = Number(this.properties.peek().min ?? 0)
    const maxValue = Number(this.properties.peek().max ?? 100)
    const stepValue = Number(this.properties.peek().step ?? 1)
    const newValue = Math.min(
      Math.max(
        Math.round(((vectorHelper.x + 0.5) * (maxValue - minValue) + minValue) / stepValue) * stepValue,
        minValue,
      ),
      maxValue,
    )
    if (this.properties.peek().value == null) {
      this.uncontrolledSignal.value = newValue
    }
    this.properties.peek().onValueChange?.(newValue)
    e.stopPropagation?.()
  }

  dispose(): void {
    this.thumb.dispose()
    this.fill.dispose()
    this.track.dispose()
    super.dispose()
  }

  add(): this {
    throw new Error(`the slider component can not have any children`)
  }
}
